a tool for shared writing and social publishing
1import { z } from "zod";
2import {
3 PullRequest,
4 PullResponseV1,
5 VersionNotSupportedResponse,
6} from "replicache";
7import type { Fact } from "src/replicache";
8import { FactWithIndexes } from "src/replicache/utils";
9import type { Attribute } from "src/replicache/attributes";
10import { makeRoute } from "../lib";
11import type { Env } from "./route";
12import type { Json } from "supabase/database.types";
13
14// First define the sub-types for V0 and V1 requests
15const pullRequestV0 = z.object({
16 pullVersion: z.literal(0),
17 schemaVersion: z.string(),
18 profileID: z.string(),
19 cookie: z.any(), // ReadonlyJSONValue
20 clientID: z.string(),
21 lastMutationID: z.number(),
22});
23
24// For the Cookie type used in V1
25const cookieType = z.union([
26 z.null(),
27 z.string(),
28 z.number(),
29 z
30 .object({
31 order: z.union([z.string(), z.number()]),
32 })
33 .and(z.record(z.string(), z.any())), // ReadonlyJSONValue with order property
34]);
35
36const pullRequestV1 = z.object({
37 pullVersion: z.literal(1),
38 schemaVersion: z.string(),
39 profileID: z.string(),
40 cookie: cookieType,
41 clientGroupID: z.string(),
42});
43
44// Combined PullRequest type
45const PullRequestSchema = z.union([pullRequestV0, pullRequestV1]);
46
47export const pull = makeRoute({
48 route: "pull",
49 input: z.object({ pullRequest: PullRequestSchema, token_id: z.string() }),
50 handler: async ({ pullRequest, token_id }, { supabase }: Env) => {
51 let body = pullRequest;
52 if (body.pullVersion === 0) return versionNotSupported;
53 let { data, error } = await supabase.rpc("pull_data", {
54 token_id,
55 client_group_id: body.clientGroupID,
56 });
57 if (!data) {
58 console.log(error);
59
60 return {
61 error: "ClientStateNotFound",
62 } as const;
63 }
64
65 let facts = data.facts as {
66 attribute: string;
67 created_at: string;
68 data: any;
69 entity: string;
70 id: string;
71 updated_at: string | null;
72 version: number;
73 }[];
74 let publication_data = data.publications as {
75 description: string;
76 title: string;
77 tags: string[];
78 cover_image: string | null;
79 preferences: Json | null;
80 }[];
81 let pub_patch = publication_data?.[0]
82 ? [
83 {
84 op: "put",
85 key: "publication_description",
86 value: publication_data[0].description,
87 },
88 {
89 op: "put",
90 key: "publication_title",
91 value: publication_data[0].title,
92 },
93 {
94 op: "put",
95 key: "publication_tags",
96 value: publication_data[0].tags || [],
97 },
98 {
99 op: "put",
100 key: "publication_cover_image",
101 value: publication_data[0].cover_image || null,
102 },
103 {
104 op: "put",
105 key: "post_preferences",
106 value: publication_data[0].preferences || null,
107 },
108 ]
109 : [];
110
111 let clientGroup = (
112 (data.client_groups as {
113 client_id: string;
114 client_group: string;
115 last_mutation: number;
116 }[]) || []
117 ).reduce(
118 (acc, clientRecord) => {
119 acc[clientRecord.client_id] = clientRecord.last_mutation;
120 return acc;
121 },
122 {} as { [clientID: string]: number },
123 );
124
125 return {
126 cookie: Date.now(),
127 lastMutationIDChanges: clientGroup,
128 patch: [
129 { op: "clear" },
130 { op: "put", key: "initialized", value: true },
131 ...(facts || []).map((f) => {
132 return {
133 op: "put",
134 key: f.id,
135 value: FactWithIndexes(f as unknown as Fact<Attribute>),
136 } as const;
137 }),
138 ...pub_patch,
139 ],
140 } as PullResponseV1;
141 },
142});
143
144const versionNotSupported: VersionNotSupportedResponse = {
145 error: "VersionNotSupported",
146 versionType: "pull",
147};